const loglevel = require('loglevel');

const methodFactory = loglevel.methodFactory;
const setLevel = loglevel.setLevel;
const originalGetLogger = loglevel.getLogger;
loglevel.methodFactory = function (methodName, logLevel, loggerName) {
	const logMethod = methodFactory(methodName, logLevel, loggerName);
	// logLevel 有毒？logMethod 返回 是对的，但logLevel 都是 0？

	return function () {
		if (arguments.length > 0) {
			const args = [].slice.call(arguments);
			const {date: dateFn, withName, withLevel, format, handle} = this.config || {
				withName : true,
				withLevel: false
			};
			let buffer = [];
			let name     = typeof withName === 'function' ? withName(loggerName) : loggerName,
			    rawLevel = methodName,
			    level    = typeof withLevel === 'function' ? withLevel(rawLevel, loglevel.getLevel(rawLevel)) : rawLevel,
			    date     = typeof dateFn === 'function' ? dateFn(new Date()) : new Date();
			if (typeof format === 'function') {
				buffer = format({rawName: loggerName, name, rawLevel, level, date, message: args});
				if (!Array.isArray(buffer))
					buffer = [buffer];
			} else {
				if (!!withName && !!name) {
					if (!!withLevel && !!level)
						name += '#' + level;
					name += ':';
					buffer.push(name);
				} else {
					if (!!withLevel && !!level)
						buffer.push(level, ':');
				}

				if (typeof date === 'function') {
					const rs = date();
					if (!!rs) buffer.push(rs);
				}

				buffer.push(...args);
			}

			if (typeof handle === 'function')
				handle(...buffer);
			else
				logMethod(...buffer);
		}
		return this;
	};
};

const binOperateCalculation = (maps) => {
	return {
		is(value, key) {
			if (typeof maps[key] === 'undefined') {
				return false;
			} else {
				return ((maps[key] & value) === maps[key]);
			}
		},
		// 用不上，就不放了
		// mapIs(value, ...keys) {
		// 	const results = {};
		// 	keys.forEach(key => results[key] = this.is(value, key));
		// 	return results;
		// },
	};
};

const CAN_TABLE = 0b1;
const CAN_COUNT = 0b10;
const CAN_PROFILE = 0b100;
const CAN_TIME = 0b1000;
const CAN_NONE = 0;
const CAN_ALL = CAN_TABLE | CAN_COUNT | CAN_PROFILE | CAN_TIME;
const addFeatures = binOperateCalculation({
	table     : CAN_TABLE,
	count     : CAN_COUNT,
	countReset: CAN_COUNT,
	profile   : CAN_PROFILE,
	profileEnd: CAN_PROFILE,
	time      : CAN_TIME,
	timeEnd   : CAN_TIME,
	timeStamp : CAN_TIME,
	timeLog   : CAN_TIME,
});

const consoleProxyCall = function (methodName, method) {
	return function () {
		let args = [].slice.call(arguments);
		const {features} = this.config || {features: CAN_NONE};
		if (!!method && addFeatures.is(features, methodName)) {
			if (methodName !== 'table') {
				const {withName} = this.config || {withName: true};
				let name = typeof withName === 'function' ? withName(this.name) : this.name;
				args = [`${name}#${args[0]}`];
			}
			method && method(...args);
		}
		return this;
	};
};

const consoleExists = typeof console !== 'undefined';
const addDescriptors = {
	config   : {value: {}, enumerable: true, writable: true, configurable: true}, // 事先定义，性能更高
	setup    : {
		value       : function (config) {
			if (isObject(config))
				this.config = Object.assign(this.config || {}, config);
			return this;
		},
		enumerable  : true,
		writable    : true,
		configurable: true
	},
	getLogger: {value: (name) => getLogger(name), enumerable: true, writable: true, configurable: true},
	setLevel : {value: (level) => setLevel(level, false), enumerable: true, writable: true, configurable: true}
};
const toString = (item) => {
	let t = typeof item;
	if (t === 'undefined' || item === null) return '';
	else if (t === 'function') return toString(item());
	else if (!!item && item.toString) return item.toString();
	return '' + item;
};
// 神经一样的 logger name
const filterLoggerName = (name) => {
	name = toString(name).replace(/[\/\:]/g, separator)
	                     .replace(/[\.]+/g, separator)
	                     .replace(/((^\.+|\.+$)|[\r\n\s\t])/g, '');
	if (name === '')
		name = defaultName;
	return name;
};
const isObject = (item) => !Array.isArray(item) && Object.prototype.toString.call(item) === '[object Object]';
const enumerableProp = (value) => ({value, enumerable: true, writable: false, configurable: false});

[
	'table',
	'count', 'countReset',
	'profile', 'profileEnd',
	'time', 'timeEnd', 'timeStamp', 'timeLog'
].forEach(n => addDescriptors[n] = {
	value       : consoleProxyCall(n, consoleExists && typeof console[n] === 'function' ? console[n] : false),
	configurable: true,
	enumerable  : true,
	writable    : true
});

const defaultName = 'default';
const globalConfig = {
	[defaultName]: {
		level    : 'TRACE',
		date     : false,
		withName : true,
		withLevel: false,
		features : CAN_NONE,
	}
};
const loggers = {};
const separator = '.';

const log = {};

const selectConfig = (name) => {
	// 这个方法作为私有方法，这里不再对 name 进行过滤了
	// 命中就没什么事情可搞了
	if (globalConfig[name]) return globalConfig[name];
	// 祖传匹配算法
	let index = name.lastIndexOf(separator);
	let slice = name;
	while (index > -1) {
		slice = slice.substring(0, index);
		index = slice.lastIndexOf(separator);
		if (globalConfig[slice]) {
			// 合并一下，这样能得到完整的 config
			return Object.assign({}, globalConfig[defaultName], globalConfig[slice]);
		}
	}
	return globalConfig[defaultName];
};

const getLogger = (name) => {
	name = filterLoggerName(name);
	if (typeof loggers[name] === 'undefined') {
		const config = selectConfig(name);
		// console.time(`init logger '${name}'`);
		const logger = loglevel.getLogger(name);
		Object.defineProperties(logger, addDescriptors);
		logger.setLevel(config.level, false);
		logger.config = config; // 这是很奇怪的，为啥直接 selectConfig(name) 赋值需要增加 10% 的性能，奇怪
		loggers[name] = logger;
		// console.timeEnd(`init logger '${name}'`);
	}
	return loggers[name];
};

const setup = (name, config) => {
	if (isObject(name)) {
		Object.keys(name).forEach(key => setup(key, name[key]));
	} else {
		name = filterLoggerName(name);
		if (!!loggers[name]) {
			loggers[name].setup(config);
		} else {
			if (!isObject(config))
				config = {level: config};
			globalConfig[name] = Object.assign(globalConfig[name] || {}, config);
		}
	}
	return log;
};

module.exports = Object.defineProperties(log, {
	canAll       : enumerableProp(CAN_ALL),
	canNone      : enumerableProp(CAN_NONE),
	canTable     : enumerableProp(CAN_TABLE),
	canCount     : enumerableProp(CAN_COUNT),
	canProfile   : enumerableProp(CAN_PROFILE),
	canTime      : enumerableProp(CAN_TIME),
	getLogger    : enumerableProp(getLogger),
	setup        : enumerableProp(setup),
	methodFactory: {
		value       : methodFactory,
		enumerable  : true,
		writable    : true,
		configurable: true,
	},
	rootLogger   : enumerableProp(loglevel),
});